''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Battery Management Program for Caravan 12V System        '
' Copyright Doug Pankhurst May 2012                        '
' Written for MMBasic V4.3a, copyright Geoff Graham 2012   '
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' File Version:    2.04 May 2013
'
' For a detailed explanation of the program, it's
' construction and rationale, please read the
' CVBMREAD.TXT file.

' Main Entry to Program

EntryPoint:  SetTick 0,0     ' Disable timer interrupt

' Global Variables - initialised to default values
BVolts = 12.8    '  Battery Voltage read from Pin A0. Read as
' 1/6th of real voltage via 6:1 resistive divider

Dim BVArray(29)  ' Battary Volts 30 element array - 1 each 2 secs

BatCharge = 100  ' Battery state of charge as a percentage where
' 12.75V or higher is 100%, down to 12.05V being 35% - danger level

AverageBV=12.8   ' Average battery voltage over last 60 seconds

Dim BVAArray(59) ' 1 hours worth of minute average battery volts

DeltaBV = 0.1    ' Battery voltage variation over 60 seconds. The
' difference between the first and the last used to
' calculate the "Time to 30% discharge point"

Time2Discharge = 20 ' Time to 30% discharge

SVolts = 20      ' Solar Array voltage from Pin A1. Read as 1/10th
' of real voltage by 10:1 resistive divider

Dim SVArray(29)  ' Solar Volts 30 element array - 1 each 2 secs

AverageSV = 21   ' Average Solar Panel volts over last 60 seconds

Dim SVAArray(59) ' 1 hours worth of minute average solar volts

InputCurrent = 18  ' Charging current into battery/load read from Pin A2
' 2.5V reading = 50mA across the shunt, which equals 50A through the shunt.
' This can be both battery charging current and load current.

Dim ICArray(29)  ' Input Current 30 element array

AverageIC = 18   ' Average input (generator) current over last 60 seconds

Dim ICAArray(59) ' array with 1 hours worth of minute average input currents

LoadCurrent = 12 ' Load current of caravan - can be supplied from the
' batteries and the charging systems (solar, AC Mains or generator). Pin A3

Dim LCArray(29)  ' Load Current 60 element array

AverageLC = 12   ' Average load current over last 60 seconds

Dim LCAArray(59) ' array to hold 1 hours worth of average load currents

BatCurrent = 6   ' A calculated value; if positive, = a charging current to
' the battery; if negative, equals a discharge current from the battery.

AverageBC = 6    ' Average charge/discharge current over last min
' calculated by AverageLC-AverageIC

InputCurrentAH = 0 ' Input current as an amp hour calculated value -
' this is an accumulating value over 24 hours

PreviousICAH = 0   ' yesterdays Input Current AHs

LoadCurrentAH = 0  ' Load current as an amp hour calculated value -
' this is an accumulating value over 24 hours

PreviousLCAH = 0   ' yesterdays Load Current AHs

BatCurrentAH = 0   ' Bat charge (pos value) or discharge (neg value)
' as an amp hour calculated value  - accumulating value over 24 hours

PreviousBCAH = 5   ' yesterdays Bat Current AHs

LogArray$ = ""     ' String to use for plugging into log array

NewDataFlag = 0    ' Two second flag - set every 2 seconds by the
' interrupt routine when new voltage and current values are collected.

MinFlag = 0        ' Minute flag - set once every min at 00 seconds
' by sec interrupt process and cleared by min processing code.

HourFlag = 0       ' Hour Flag - set on the hour to enable
' writing the 60 average readings in the log array to be writtent to file

EODFlag = 0        ' End of Day Flag - set at 6AM to enable daily log file
' to be closed and new log file created by LogFileCreate subroutine

'''''''''''''''''
' Carry out initialisation routines here - enable interrupt last

Entry:
Mode 3,7     ' 8 colour mode white on black
Colour White,Black
Cls

' Display all static data
Option USB Off    ' only to VGA
DisplayStatic
BarGraph
' Create Log File on program entry and then at start of new day - 6AM
CreateLogFile

' Set up all initial values to default for orderly entry.
PreviousICAH = 50
PreviousLCAH = 45
PreviousBCAH = 5
OldABV = 13
LFAPointer = 0

' Fill the arrays
For Ax=0 To 29
  BVArray(Ax) = 12.8
  SVArray(Ax) = 19
  ICArray(Ax) = 18
  LCArray(Ax) = 12
Next Ax

' Wait until 00 seconds to start for clean entry.
Do While Val(Mid$(Time$,7,2)) <> 58
Loop

' Enable timer tick interrupts to start data collection at 00 seconds
SetTick 2000,GetData   ' Every other second, get battery voltage and current
' data through the GetData interrupt routine.

' end of initialisation routines
''''''''''''''''''''
' Main program loop

Main:
Do While NewDataFlag = 0   ' wait until interrupt provides fresh data
Loop

''''''''''''''''''''
' 2 second processing code
' Write out time and date to display
Print @(1,1)Time$ + "   " + Date$

' Push most recent values into arrays for minute averaging
For Ax = 29 To 1 Step -1
  BVArray(Ax) = BVArray(Ax-1)
  SVArray(Ax) = SVArray(Ax-1)
  ICArray(Ax) = ICArray(Ax-1)
  LCArray(Ax) = LCArray(Ax-1)
Next Ax
BVArray(0) = BVolts
SVArray(0) = SVolts
ICArray(0) = InputCurrent
LCArray(0) = LoadCurrent
' Charge/Discharge Current is InputCurrent-LoadCurrent - if positive
'  it is a charging current into battery, if negative, it is a
'  discharging current out of battery
BatCurrent = InputCurrent-LoadCurrent

' Convert both input, output & battery instantaneous currents into Amp Hours
InputCurrentAH = InputCurrentAH + (InputCurrent/1800) ' use 2 sec interrupt
LoadCurrentAH = LoadCurrentAH + (LoadCurrent/1800)    ' rate for calculation
BatCurrentAH = InputCurrentAH-LoadCurrentAH

' Note: All AH accumulating totals are plugged into last day values
' then reset to zero at 6AM - now update terminal display with all values
Font #2
Colour 2
Print @(372,25)Format$(BatCharge,"%3.0f")+"%"
Font #1
Colour 7
Print @(123,45)Format$(BVolts,"%5.2fV")
Print @(123,56)Format$(SVolts,"%5.2fV")
Print @(123,67)Format$(InputCurrent,"%5.2fA")
Print @(123,78)Format$(LoadCurrent,"%5.2fA")
Print @(312,45)Format$(AverageBV,"%5.2fV")
Print @(312,56)Format$(AverageSV,"%5.2fV")
Font #2
Colour 7
Print @(420,184)Format$(AverageBV,"%4.1f")
Colour 3
Print @(420,232)Format$(AverageSV,"%4.1f")
Colour 7
Font #1
Print @(376,149)Format$(Time2Discharge,"%4.0f min")
Print @(312,67)Format$(AverageIC,"%5.1fA")
Print @(312,78)Format$(AverageLC,"%5.1fA")
Colour 6
Font #2
Print @(420,285)Format$(AverageIC,"%4.1f")
Colour 5
Print @(420,340)Format$(AverageLC,"%4.1f")
Colour 7
Font #1
Print @(117,127)Format$(InputCurrentAH,"%5.1fahrs")
Print @(117,138)Format$(LoadCurrentAH,"%5.1fahrs")
If (Sgn(BatCurrentAH) = +1) Then
  Colour 2
  Print @(117,149)Format$(BatCurrentAH,"%+5.1fahrs")
Else
  Colour 4
  Print @(117,149)Format$(BatCurrentAH,"%+5.1fahrs")
EndIf
Colour 7
Print @(312,127)Format$(PreviousICAH,"%5.1fahrs")
Print @(312,138)Format$(PreviousLCAH,"%5.1fahrs")
If (Sgn(PreviousBCAH) = +1) Then
  Colour 2
  Print @(312,149)Format$(PreviousBCAH,"%+5.1fahrs")
Else
  Colour 4
  Print @(312,149)Format$(PreviousBCAH,"%+5.1fahrs")
EndIf
Colour 7
If (Sgn(AverageBC) = +1) Then
  Colour 2
  Print @(312,89)Format$(AverageBC,"%+5.1fA")
  Font #2
  Print @(418,410)Format$(AverageBC,"%+4.1f")
  Font #1
Else
  Colour 4
  Print @(312,89)Format$(AverageBC,"%+5.1fA")
  Font #2
  Print @(418,410)Format$(AverageBC,"%+4.1f")
  Font #1
EndIf
If (Sgn(BatCurrent) = +1) Then
  Colour 2
  Print @(123,89)Format$(BatCurrent,"%+5.2fA")
Else
  Colour 4
  Print @(123,89)Format$(BatCurrent,"%+5.2fA")
EndIf
Colour 7
NewDataFlag = 0      ' clear new data flag ready for next interrupt
'
' End of two second processing code, now check for minute, hour or end of
' day flags and process accordingly
'''''''''''''''''''''''''''''''''''
' Check for minute flag, calculate the average of the last 60 seconds of
' readings, plug them into the log file array at the log file array pointer
If MinFlag = 1 Then
  ' calculate average for last minute
  OldABV = AverageBV
  OldASV = AverageSV
  AverageBV = 0
  AverageSV = 0
  AverageIC = 0
  AverageLC = 0
  For Ax = 0 To 29   'don't forget only sampled every 2 secs
    AverageBV = AverageBV + BVArray(Ax)
    AverageSV = AverageSV + SVArray(Ax)
    AverageIC = AverageIC + ICArray(Ax)
    AverageLC = AverageLC + LCArray(Ax)
  Next Ax
  AverageBV = AverageBV/30
  AverageSV = AverageSV/30
  AverageIC = AverageIC/30
  AverageLC = AverageLC/30
  AverageBC = AverageIC - AverageLC
  ' Calculate battery change as percentage and fill charge state graph
  ' now calculate charge and paint capacity graph
  ' 12.1V equates to 30% charge
  BatCharge = Int((AverageBV - 11.75) * 100)
  If BatCharge < 30 Then BatCharge = 30
  If BatCharge > 100 Then BatCharge = 100
  FillBarGraph(BatCharge)

  ' Calculate time to 30% discharge of battery
  DeltaBV = OldABV - AverageBV
  If Sgn(DeltaBV) = +1 Then
       DVRate = BVArray(0)         ' set initial values for rate of
    Time2Discharge = 0             ' discharge and time to discharge
    Do While DVRate > 12.1         ' 12.1 volts equates to 30% discharge
      DVRate = DVRate - DeltaBV    ' loop through calculating the amount
                                   ' of time to get the current voltage
                                   ' down to the 30% discharge point
      Time2Discharge = Time2Discharge + 1 ' inc minutes to discharge count
    Loop
  Else
    Print @(376,149)"Charging "   ' if current voltage is greater than
  EndIf                            ' previous minute
  BVAArray(LFAPointer) = AverageBV  ' update average arrays
  SVAArray(LFAPointer) = AverageSV
  ICAArray(LFAPointer) = AverageIC
  LCAArray(LFAPointer) = AverageLC
  LFAPointer = LFAPointer + 1          ' LFA pointer reset by eod routine

' Now paint out running graph
' - first work out the current minute hour combo in minutes
  rg_mins = (((Val(Mid$(Time$,1,2)) Mod 6)*60))+(Val(Mid$(Time$,4,2)))
  rg_mins = rg_mins + 30               ' offset to start of graph
  rg_abv = 412 - Int((AverageBV-8) * 10)
    If rg_abv > 412 Then rg_abv = 412
  rg_asv = 412 - Int((AverageSV-8) * 10)
    If rg_asv > 412 Then rg_asv = 412
    If rg_asv < 163 Then rg_asv = 163
  rg_aic = 412 - Int(AverageIC * 5)
  rg_alc = 412 - Int(AverageLC * 5)
  If (Sgn(AverageBC) = +1) Then
    rg_abc = 412 - Int(AverageBC * 5)
  Else
    rg_abc = 412 + Int(AverageBC * 5)
  EndIf
  For yrg = 163 To 412 Step 1
    If rg_abc = yrg Then
      If (Sgn(AverageBC) = +1) Then
        Pixel(rg_mins,yrg) = 2
      Else
        Pixel(rg_mins,yrg) = 4
      EndIf
    Else
      Pixel(rg_mins,yrg) = 0
    EndIf
    If rg_abv = yrg Then
      Pixel(rg_mins,yrg) = 7
    EndIf
    If rg_asv = yrg Then
      Pixel(rg_mins,yrg) = 3
    EndIf
    If rg_aic = yrg Then
      Pixel(rg_mins,yrg) = 6
    EndIf
    If rg_alc = yrg Then
      Pixel(rg_mins,yrg) = 5
    EndIf
    Line(rg_mins+1,yrg)-(rg_mins+3,yrg),0
  Next yrg

  MinFlag = 0   ' reset the min flag
EndIf
' End of minute code
''''''''''''''''''''
' Check for hour flag - if true then append log array out to file
' (we want to limit log file writes to 24 per day to keep
' SD card writes as low as possible).

If HourFlag = 1 Then        ' set each hour in interrupt routine
  LogArray$ = ""
  For x=0 To 59 Step 1  ' get the average values from each average array
    LogArray$ = LogArray$+Format$(BVAArray(x),"%+6.2fV")+Chr$(44)
    LogArray$ = LogArray$+Format$(SVAArray(x),"%+6.2fV")+Chr$(44)
    LogArray$ = LogArray$+Format$(ICAArray(x),"%+5.1fA")+Chr$(44)
    LogArray$ = LogArray$+Format$(LCAArray(x),"%+5.1fA")
    Print #1,LogArray$
    LogArray$ = ""
  Next x
  LFAPointer = 0   ' reset array pointer
  HourFlag = 0     ' clear hour flag until next hour
EndIf
' End of hour code
''''''''''''''''''
' Check for end of day - if true then close log file and create new file
' for new day starting at 6AM
If EODFlag = 1 Then
  Close #1       ' close out last log file, save amp hour data
  PreviousICAH = InputCurrentAH
  InputCurrentAH = 0
  PreviousLCAH = LoadCurrentAH
  LoadCurrentAH = 0
  PreviousBCAH = BatCurrentAH
  BatCurrentAH = 0
  CreateLogFile  ' create new log file with todays date
  EODFlag = 0    ' clear end of day flag until next 6AM
EndIf
' End of day code
'''''''''''''''''
GoTo Main        ' back to Main ready for next interrupt

' End of main program loop
''''''''''''''''''''''''''
''''''''''''''''''''''''''
' Interrupt Routine Handling

GetData:      ' Timer Interrupt handler
              ' Arduino analogue input pins
  SetPin a0,1  ' pin 35 - battery voltage - 0 to 2.5V = 0 to 15V
  SetPin a1,1  ' pin 36 - solar volts - 0 to 2.5V = 0 to 25V
  SetPin a2,1  ' pin 37 - input current shunt - 0 to 2.5V = 0 to 50A
  SetPin a3,1  ' pin 38 - load current shunt - 0 to 2.5V = 0 to 50A

' Get the battery voltage
  ic=0
  BVolts = 0
  GetPin35 = 0
  For ic = 1 To 20
    GetPin35 = Pin(a0)
    BVolts = BVolts + GetPin35
  Next ic
  BVolts = BVolts/20  ' Average out Battery voltage
  BVolts = BVolts*6   ' 15 battery volts = 2.5 MM volts

' Get the Solar Array voltage
  ic=0
  SVolts=0
  GetPin36 = 0
  For ic = 1 To 20
    GetPin36 = Pin(a1)
    SVolts = SVolts + GetPin36
  Next ic
  SVolts = SVolts/20  ' same as for BVolts
  SVolts = SVolts*10  ' 25 solar volts = 2.5 MM volts

' Get the input current as a voltage
  InputCurrent = 0
  GetPin37 = 0
  For ic = 1 To 20
    GetPin37 = Pin(a2) - 0.5817
    InputCurrent = InputCurrent + GetPin37
  Next ic
  InputCurrent = InputCurrent/20   ' Average out input current
  InputCurrent = Abs(InputCurrent*16.667)   ' Correct for opamp amplification
' current by 16.667 now gives a value 50mV = 1A

' Get the load current as a voltage
  LoadCurrent = 0
  GetPin38 = 0
  For ic = 1 To 20
    GetPin38 = Pin(a3) - 0.5803
    LoadCurrent = LoadCurrent + GetPin38
  Next ic
  LoadCurrent = LoadCurrent/20 ' Average out load current
  LoadCurrent = Abs(LoadCurrent*16.667) ' Correct for opamp amplification
' current by 16.667 now gives a value 50mV = 1A

' Get the current time, extract the minutes component
If Mid$(Time$,7,2) = "00" Then
  MinFlag = 1
EndIf

' Get the current time, test for hour AND minute tunover
If Mid$(Time$,7,2) = "00" And Mid$(Time$,4,2) = "00" Then
  HourFlag = 1
EndIf

' Get the current time, test for 0600:00
If MinFlag And HourFlag And Mid$(Time$,1,2)="06" Then
  EODFlag = 1
EndIf

NewDataFlag = 1 ' set to indicate fresh data

IReturn  ' Returns with the following values:-
' BVolts = 0 to 15 equalling battery volts
' SVolts = 0 to 25 equalling solar Array volts
' InputCurrent=0 to 50 equalling 0 to 50Amps
' LoadCurrent=0 to 50 equalling 0 to 50Amps
'End of Interrupt code
'''''''''''''''''''''
' Defined Subroutines
'''''''''''''''''''''
' Set up terminal display with static data (field names etc.)
' - uses the PRINT @ to print at defined positions

Sub DisplayStatic
Local xrg,yrg,volts,amps
  Cls
  Print @(150,1)"CARAVAN POWER MONITOR V2.04"
  Line(150,11)-(312,11)
  Print @(1,25)"Instantaneous Readings"
  Print @(198,25)"Minute Average Readings"
  Line(1,36)-(152,36)
  Line(198,36)-(346,36)
  Print @(376,1)"Battery"
  Print @(1,45)"Battery Volts     ="
  Print @(198,45)"Battery Volts     ="
  Print @(379,12)"Charge"
  Print @(1,56)"Solar Array Volts ="
  Print @(198,56)"Solar Array Volts ="
  Print @(1,67)"Input Current     ="
  Print @(198,67)"Input Current     ="
  Print @(1,78)"Load Current      ="
  Print @(198,78)"Load Current      ="
  Print @(1,89)"Bat Chg/Dis Curr  ="
  Print @(198,89)"Bat Chg/Dis Curr  ="
  Print @(1,108)"Daily Accumulating Readings"
  Print @(198,108)"Previous Days Total Readings"
  Line(1,120)-(170,120)
  Line(198,120)-(362,120)
  Print @(1,127)"Input Charge      ="
  Print @(198,127)"Input Charge      ="
  Print @(384,127)"Time to"
  Print @(1,138)"Load Discharge    ="
  Print @(198,138)"Load Discharge    ="
  Print @(400,138)"30%"
  Print @(1,149)"Battery Chg/DisChg="
  Print @(198,149)"Battery Chg/DisChg="
  Line(0,162)-(479,431),1,B
  Colour 7,0
  Print @(424,164)"Battery"
  Print @(430,174)"Volts"
  Colour 3,0
  Print @(430,210)"Solar"
  Print @(430,220)"Volts"
  Colour 6,0
  Print @(430,260)"Input"
  Print @(433,270)"Amps"
  Colour 5,0
  Print @(433,315)"Load"
  Print @(433,325)"Amps"
  Colour 2,0
  Print @(427,375)"Battery"
  Print @(436,385)"Amps"
  Print @(430,395)"In/"
  Colour 4,0
  Print @(449,395)"Out"
  Colour 7,0
  volts=22
  amps = 48
  For yrg =167 To 387 Step 10
    Print @(2,yrg)Format$(volts,"%2.0fV")
    Print @(400,yrg)Format$(amps,"%2.0fA")
    amps=amps-2
    yrg=yrg+10
    Print @(400,yrg)Format$(amps,"%2.0fA")
    volts=volts-1
    amps=amps-2
  Next yrg
  Line(29,163)-(29,413)
  Line(393,163)-(393,413)
  For yrg = 163 To 413 Step 10 'minor ticks
    Pixel(28,yrg)=7
    Pixel(394,yrg)=7
  Next yrg
  For yrg = 173 To 413 Step 20  'major ticks
    Pixel(27,yrg)=7
    Pixel(395,yrg)=7
  Next yrg
  Line(30,414)-(390,414) 'now for the bottom
  For xrg = 30 To 390 Step 5
    Pixel(xrg,415)=7
  Next xrg
  For xrg = 30 To 390 Step 15
    Line(xrg,415)-(xrg,416),7
  Next xrg
  For xrg = 30 To 390 Step 30
    Line(xrg,415)-(xrg,417),7
  Next xrg
  For xrg = 30 To 390 Step 60
    Line(xrg,415)-(xrg,418),7
  Next xrg
  Print @(100,419)"6 Hour Graphing period - starts 6AM"
End Sub
'''''''
' Bar Graph - Draw Static
' top left x=432, y=0, bottom right x=479, y=162
' This equates to 2 steps per percentage for the range of 35% to 100%
Sub BarGraph
  Local xtl,ytl,xbr,ybr,percent,xpos,ypos,y

  xtl=432:ytl=0:xbr=xtl+47:ybr=ytl+162
'  First draw the box and values
  Line(xtl,ytl)-(xbr,ybr),1,B
  percent=100
  Print @(xtl+3,ytl+2)"Battery"
  Print @(xtl+13,ytl+13)100
  Print @(xtl+40,ytl+13)"%"
  percent=percent-5
  For y=ytl+22 To 142 Step 10
    Print @(xtl+19,y)percent
    Print @(xtl+40,y)"%"
    percent = percent-5
  Next y
End Sub
'''''''
Sub CreateLogFile
  LogFileName$="B:"+Mid$(Date$,9,2)+Mid$(Date$,4,2)+Left$(Date$,2)+".CSV"
  Open LogFileName$ For Output As #1
  Print #1, "Battery Voltage";Chr$(44);
  Print #1, "Solar Array Voltage";Chr$(44);
  Print #1, "Input Current";Chr$(44);
  Print #1, "Load Current"
End Sub
'''''''
' Bar Graph - Fill
' top left x=432, y=0, bottom right x=479, y=162
' To fill, start at x=434 thru 443 and y=150 back up to y=12
' This gives 138 steps, equates to 1.97 steps per % for 30% to 100%
' Enter with actual battery charge as a percent

Sub FillBarGraph(BatCharge)
Local xtl,ytl,bat_chg,xpos,ypos,yper,xright,xleft,ybot,ytop
bat_chg=BatCharge
xtl=432:ytl=0:xbr=xtl+47:ybr=ytl+162
' Now fill the bar graph
If bat_chg < 31 Then
  bat_chg=30
  Colour 4,7
  Print @(xtl + 4,ytl + 151,2)"DANGER"
Else
  Colour 2,7
  Print @(xtl + 4,ytl + 151,2)"  OK  "
EndIf
Colour 7,0
If bat_chg > 100 Then bat_chg=100
ytop=12
yper=Int(((100 - bat_chg) * 1.97) + 12)
ybot=150
xleft=437
xright=446
For ypos=ybot To ytop Step -1
  If yper < ypos Then
    For xpos=xleft To xright Step 1
      Pixel(xpos,ypos)=2
    Next xpos
  Else
    For xpos=xleft To xright Step 1
      Pixel(xpos,ypos)=0
    Next xpos
  EndIf
Next ypos
End Sub
' End program.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

